package cn.lenchu.core.service.impl;

import cn.lenchu.core.model.GroupDetail;
import cn.lenchu.core.model.GroupItem;
import cn.lenchu.core.model.GroupSummary;
import cn.lenchu.core.service.GroupService;
import cn.lenchu.data.dao.GroupDao;
import cn.lenchu.data.dao.GroupStockDao;
import cn.lenchu.data.model.Group;
import cn.lenchu.data.model.GroupStock;
import cn.lenchu.stock.model.Stock;
import cn.lenchu.stock.service.StockService;
import cn.lenchu.util.DateTimeUtil;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author lenchu
 */
@Service
public class GroupServiceImpl implements GroupService {

    @Autowired
    private GroupDao groupDao;
    @Autowired
    private GroupStockDao groupStockDao;

    @Autowired
    private StockService stockService;

    @Override
    public GroupSummary upsertGroup(Integer userId, Integer groupId, String groupName, Double cash) {
        Group group = new Group();
        group.setId(groupId);
        group.setName(groupName);
        group.setUserId(userId);
        group.setCash(cash);
        if (groupId == null) {
            group.setCreateTime(DateTimeUtil.dateTimeToTimestamp(LocalDateTime.now()));
        }
        group = groupDao.upsert(group);
        return toGroupSummary(group);
    }

    @Override
    public List<GroupSummary> listUserGroup(Integer userId) {
        List<Group> groupList = groupDao.listByUserId(userId);
        return groupList.stream()
            .map(this::toGroupSummary)
            .collect(Collectors.toList());
    }

    @Override
    public GroupDetail groupDetailById(Integer groupId) {
        Group group = groupDao.getById(groupId, true);
        return toGroupDetail(group);
    }

    @Override
    public GroupDetail deleteGroupById(Integer groupId) {
        Group group = groupDao.deleteById(groupId);
        List<GroupStock> groupStockList = groupStockDao.deleteByGroupId(groupId);
        group.setGroupStockList(groupStockList);
        return toGroupDetail(group);
    }

    @Override
    public GroupItem createGroupItem(Integer groupId, String code, String name, Double count, Double price, Integer sort) {
        GroupStock groupStock = new GroupStock();
        groupStock.setGroupId(groupId);
        groupStock.setCode(code);
        groupStock.setName(name);
        groupStock.setCount(count);
        groupStock.setPrice(price);
        groupStock.setSort(sort);
        groupStock = groupStockDao.upsert(groupStock);
        return toGroupItem(groupStock);
    }

    @Override
    public GroupItem updateGroupItem(Integer id, Double count, Double price, Integer sort) {
        GroupStock groupStock = new GroupStock();
        groupStock.setId(id);
        groupStock.setCount(count);
        groupStock.setPrice(price);
        groupStock.setSort(sort);
        groupStock = groupStockDao.upsert(groupStock);
        return toGroupItem(groupStock);
    }

    @Override
    public GroupItem deleteGroupItem(Integer id) {
        GroupStock groupStock = groupStockDao.deleteById(id);
        return toGroupItem(groupStock);
    }

    private void flushNameAndPrice(GroupStock groupStock, Stock stock) {
        if (stock == null) {
            return;
        }
        if (stock.dataComplete()) {
            groupStock.setName(stock.getName());
            groupStock.setPrice(halfUp(stock.getPrice(), 4));
            groupStock.setUpdateTime(stock.getUpdateTime());
        }
    }

    @Override
    public GroupDetail refreshGroup(Integer groupId) {
        Group group = groupDao.getById(groupId, true);
        List<GroupStock> groupStockList = Optional.ofNullable(group)
            .map(Group::getGroupStockList)
            .orElse(new ArrayList<>());
        if (!groupStockList.isEmpty()) {
            Map<String, Stock> stockMap = stockService.batchGetByCode(groupStockList.stream().map(GroupStock::getCode).collect(Collectors.toList()));
            for (GroupStock groupStock : groupStockList) {
                flushNameAndPrice(groupStock, stockMap.get(groupStock.getCode()));
                groupStockDao.upsert(groupStock);
            }
        }
        return toGroupDetail(group);
    }

    private GroupSummary toGroupSummary(Group group) {
        if (group == null) {
            return null;
        }
        GroupSummary summary = new GroupSummary();
        BeanUtils.copyProperties(group, summary);
        return summary;
    }

    private GroupDetail toGroupDetail(Group group) {
        if (group == null) {
            return null;
        }
        GroupDetail detail = new GroupDetail();
        BeanUtils.copyProperties(group, detail);
        List<GroupStock> groupStockList = group.getGroupStockList();
        if (groupStockList != null) {
            List<GroupItem> groupItemList = groupStockList.stream()
                .map(this::toGroupItem)
                .filter(Objects::nonNull)
                .sorted(Comparator.comparingInt(groupItem -> groupItem.getSort() != null ? groupItem.getSort() : Integer.MAX_VALUE))
                .collect(Collectors.toList());
            detail.setGroupItemList(groupItemList);
            double detailCash = group.getCash() == null ? 0d : group.getCash();
            double groupTotal = detailCash;
            for (GroupItem groupItem : groupItemList) {
                groupTotal = groupTotal + groupItem.getTotal();
            }
            for (GroupItem groupItem : groupItemList) {
                groupItem.setPosition(calculatePosition(groupItem.getTotal(), groupTotal));
            }
            detail.setCashPosition(calculatePosition(detailCash, groupTotal));
        }
        return detail;
    }

    private double calculatePosition(double total, double groupTotal) {
        double position = total / groupTotal * 100;
        return halfUp(position, 2);
    }

    private double halfUp(double value, int scale) {
        BigDecimal bigDecimal = new BigDecimal(value).setScale(scale, RoundingMode.HALF_UP);
        return bigDecimal.doubleValue();
    }

    private GroupItem toGroupItem(GroupStock groupStock) {
        if (groupStock == null) {
            return null;
        }
        GroupItem item = new GroupItem();
        BeanUtils.copyProperties(groupStock, item);
        if (groupStock.getCount() != null && groupStock.getPrice() != null) {
            item.setTotal(halfUp(groupStock.getCount() * groupStock.getPrice(), 2));
        } else {
            item.setTotal(0d);
        }
        return item;
    }
}
